﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using CompareAttribute = System.ComponentModel.DataAnnotations.CompareAttribute;

namespace Library.MVCHelpers
{
    public sealed class CultureRequiredAttribute : RequiredAttribute
    {
        public override string FormatErrorMessage(string name)
        {
            return string.Format(NOWCulture.GetString("{0} is required."), NOWCulture.GetString(name));
        }
    }

    public sealed class CultureCompareAttribute : CompareAttribute
    {
        public override string FormatErrorMessage(string name)
        {
            return string.Format(NOWCulture.GetString("{0} and {1} do not match."), NOWCulture.GetString(OtherProperty), NOWCulture.GetString(name));
        }

        public CultureCompareAttribute(string otherProperty)
            : base(otherProperty)
        {
        }
    }

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class CulturePhoneAttribute : DataTypeAttribute
    {
        private static readonly Regex Regex = new Regex("^(\\+\\s?)?((?<!\\+.*)\\(\\+?\\d+([\\s\\-\\.]?\\d+)?\\)|\\d+)([\\s\\-\\.]?(\\(\\d+([\\s\\-\\.]?\\d+)?\\)|\\d+))*(\\s?(x|ext\\.?)\\s?\\d+)?$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled);

        public CulturePhoneAttribute()
            : base(DataType.PhoneNumber)
        {
            ErrorMessage = string.Format(NOWCulture.GetString("{0} is not a valid."), NOWCulture.GetString("Phone number"));
        }

        public override bool IsValid(object value)
        {
            if (value == null)
                return true;
            string input = value as string;
            if (input != null)
                return Regex.Match(input).Length > 0;
            return false;
        }
    }

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class CultureEmailAddressAttribute : DataTypeAttribute
    {
        private static readonly Regex Regex = new Regex("^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled);

        public CultureEmailAddressAttribute()
            : base(DataType.EmailAddress)
        {
            this.ErrorMessage = string.Format(NOWCulture.GetString("{0} is not a valid."), NOWCulture.GetString("Email"));
        }

        public override bool IsValid(object value)
        {
            if (value == null)
                return true;
            string input = value as string;
            if (input != null)
                return Regex.Match(input).Length > 0;
            return false;
        }
    }

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class CultureDateDisplayFormatAttribute : DisplayFormatAttribute
    {
        public CultureDateDisplayFormatAttribute()
            : base()
        {
            DataFormatString = NOWCulture.CurrentCultureInfo.DateTimeFormat.ShortDatePattern;
            ApplyFormatInEditMode = true;
        }
    }

    public class NOWDateTimeModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var displayFormat = bindingContext.ModelMetadata.DisplayFormatString;
            var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

            if (!string.IsNullOrEmpty(displayFormat) && value != null && !string.IsNullOrEmpty(value.AttemptedValue))
            {
                DateTime date;
                displayFormat = displayFormat.Replace("{0:", string.Empty).Replace("}", string.Empty);
                // use the format specified in the DisplayFormat attribute to parse the date
                if (DateTime.TryParseExact(value.AttemptedValue, displayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
                {
                    return date;
                }
                bindingContext.ModelState.AddModelError(
                    bindingContext.ModelName,
                    string.Format(NOWCulture.GetString("The value '{0}' is not valid for {1}."), value.AttemptedValue, NOWCulture.GetString(bindingContext.ModelName))
                    );
            }

            return base.BindModel(controllerContext, bindingContext);
        }
    }

    public sealed class CultureMaxLengthAttribute : MaxLengthAttribute
    {
        public CultureMaxLengthAttribute(int length)
            : base(length)
        {

        }

        public override string FormatErrorMessage(string name)
        {
            return string.Format(NOWCulture.GetString("{0} must not exceed {1} characters."), NOWCulture.GetString(name), Length);
        }
    }

    public sealed class CultureMinLengthAttribute : MinLengthAttribute
    {
        public CultureMinLengthAttribute(int length)
            : base(length)
        {
        }

        public override string FormatErrorMessage(string name)
        {
            return string.Format(NOWCulture.GetString("{0} must be at least {1} characters."), NOWCulture.GetString(name), Length);
        }
    }

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class CultureFileExtensionsAttribute : DataTypeAttribute
    {
        private string _extensions;

        public string Extensions
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(this._extensions))
                    return this._extensions;
                return "png,jpg,jpeg,gif";
            }
            [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
            set
            {
                this._extensions = value;
            }
        }

        private string ExtensionsFormatted
        {
            get
            {
                return Enumerable.Aggregate<string>(this.ExtensionsParsed, (Func<string, string, string>)((left, right) => left + ", " + right));
            }
        }

        private string ExtensionsNormalized
        {
            get
            {
                return this.Extensions.Replace(" ", "").Replace(".", "").ToLowerInvariant();
            }
        }

        private IEnumerable<string> ExtensionsParsed
        {
            get
            {
                return Enumerable.Select<string, string>((IEnumerable<string>)this.ExtensionsNormalized.Split(','), (Func<string, string>)(e => "." + e));
            }
        }

        public CultureFileExtensionsAttribute()
            : base(DataType.Upload)
        {
            this.ErrorMessage = NOWCulture.GetString("Uploaded file/files is invalid.");
        }

        public override string FormatErrorMessage(string name)
        {
            return string.Format((IFormatProvider)CultureInfo.CurrentCulture, this.ErrorMessageString, new object[2]
            {
            (object) name,
            (object) this.ExtensionsFormatted
            });
        }

        public override bool IsValid(object value)
        {
            if (value == null)
                return true;
            string fileName = value as string;
            if (fileName != null)
                return this.ValidateExtension(fileName);
            return false;
        }

        private bool ValidateExtension(string fileName)
        {
            try
            {
                return Enumerable.Contains<string>(this.ExtensionsParsed, Path.GetExtension(fileName).ToLowerInvariant());
            }
            catch (ArgumentException ex)
            {
                return false;
            }
        }
    }
}
